#ifndef _SYMBOLSOURCEDIA_H_
#define _SYMBOLSOURCEDIA_H_

#include <atomic>
#include <mutex>
#include <thread>

#include "_global.h"
#include "pdbdiafile.h"
#include "symbolsourcebase.h"

class SymbolSourceDIA : public SymbolSourceBase {
  struct CachedLineInfo {
    uint32 rva;
    uint32 lineNumber;
    uint32 sourceFileIndex;
  };

  struct ScopedDecrement {
   private:
    std::atomic<duint>& _counter;

   public:
    ScopedDecrement(const ScopedDecrement&) = delete;
    ScopedDecrement& operator=(const ScopedDecrement&) = delete;
    ScopedDecrement(std::atomic<duint>& counter) : _counter(counter) {}
    ~ScopedDecrement() { _counter--; }
  };

 private:  // symbols
  std::vector<SymbolInfo> _symData;

  struct AddrIndex {
    duint addr;
    size_t index;

    bool operator<(const AddrIndex& b) const { return addr < b.addr; }
  };

  std::vector<AddrIndex> _symAddrMap;  // rva -> data index (sorted on rva)
  std::vector<NameIndex> _symNameMap;  // name -> data index (sorted on name)

 private:  // line info
  std::vector<CachedLineInfo> _linesData;
  std::vector<AddrIndex> _lineAddrMap;  // addr -> line
  std::vector<String> _sourceFiles;     // uniqueId + name

  struct LineIndex {
    uint32_t line;
    uint32_t index;

    bool operator<(const LineIndex& b) const { return line < b.line; }
  };

  std::vector<std::vector<LineIndex>>
      _sourceLines;  // uses index in _sourceFiles

 private:  // general
  HANDLE _symbolsThread = nullptr;
  HANDLE _sourceLinesThread = nullptr;
  std::atomic<bool> _requiresShutdown;
  std::atomic<duint> _loadCounter;

  bool _isOpen;
  std::string _path;
  std::string _modname;
  duint _imageBase;
  duint _imageSize;
  std::atomic<bool> _symbolsLoaded = false;
  std::atomic<bool> _linesLoaded = false;

 private:
  static int hackicmp(const char* s1, const char* s2) {
    unsigned char c1, c2;
    while ((c1 = *s1++) == (c2 = *s2++))
      if (c1 == '\0') return 0;
    s1--, s2--;
    while ((c1 = tolower(*s1++)) == (c2 = tolower(*s2++)))
      if (c1 == '\0') return 0;
    return c1 - c2;
  }

 public:
  static bool isLibraryAvailable() { return PDBDiaFile::initLibrary(); }

 public:
  SymbolSourceDIA();

  virtual ~SymbolSourceDIA() override;

  virtual bool isOpen() const override;

  virtual bool isLoading() const override;

  virtual bool cancelLoading() override;

  virtual void waitUntilLoaded() override;

  virtual bool findSymbolExact(duint rva, SymbolInfo& symInfo) override;

  virtual bool findSymbolExactOrLower(duint rva, SymbolInfo& symInfo) override;

  virtual void enumSymbols(const CbEnumSymbol& cbEnum) override;

  virtual bool findSourceLineInfo(duint rva, LineInfo& lineInfo) override;

  virtual bool findSourceLineInfo(const std::string& file, int line,
                                  LineInfo& lineInfo) override;

  virtual bool findSymbolByName(const std::string& name, SymbolInfo& symInfo,
                                bool caseSensitive) override;

  virtual bool findSymbolsByPrefix(
      const std::string& prefix,
      const std::function<bool(const SymbolInfo&)>& cbSymbol,
      bool caseSensitive) override;

  virtual std::string loadedSymbolPath() const override;

 public:
  bool loadPDB(const std::string& path, const std::string& modname,
               duint imageBase, duint imageSize,
               DiaValidationData_t* validationData);

 private:
  bool loadSymbolsAsync();
  bool loadSourceLinesAsync();
  uint32_t findSourceFile(const std::string& fileName) const;

  static DWORD WINAPI SymbolsThread(void* parameter);
  static DWORD WINAPI SourceLinesThread(void* parameter);
};

#endif  // _SYMBOLSOURCEPDB_H_
